package org.zezs.deployment;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jcraft.jsch.*;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zezs.deployment.config.*;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

public class Deploy {

    private static Logger logger = LoggerFactory.getLogger(Deploy.class);
    private Config config;

    public Deploy(String[] args) {
        load(args);
    }

    public static void main(String[] args) {
        try {
            new Deploy(args).deploy();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void load(String[] args) {
        Set<String> collect = Arrays.stream(args).collect(Collectors.toSet());
        try {
            String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
            Config o = null;
            path = java.net.URLDecoder.decode(path, "UTF-8");
            if (path.endsWith("jar")) {
                File file = new File(path);
                File file1 = new File(file.getParent(), "config.json");
                if (file1.exists()) {
                    o = JSONObject.parseObject(new FileInputStream(file1), Charset.forName("utf-8"), Config.class);
                }
            } else {
                InputStream resourceAsStream = Deploy.class.getResourceAsStream("/config.json");
                o = JSONObject.parseObject(resourceAsStream, Charset.forName("utf-8"), Config.class);
            }

            try {
                SshInfo sshInfo = o.getNginx().getSshInfo();
                JSch jsch = new JSch();
                Session session = jsch.getSession(sshInfo.getUser(), sshInfo.getHost());
                session.setPassword(sshInfo.getPasswd());
                Properties sshConfig = new Properties();
                sshConfig.put("StrictHostKeyChecking", "no");
                session.setConfig(sshConfig);
                session.connect();
                o.getNginx().setSession(session);


                String head = o.getNginx().getHead();
                int proxy_pass = head.indexOf("proxy_pass");
                if(proxy_pass==-1){
                    logger.error("未配置 nginx.head.proxy_pass");
                    throw new RuntimeException("nginx.head.proxy_pass");
                }
                String substring = head.substring(proxy_pass);
                int i = substring.indexOf(";");
                String proxy_pass1 = substring.substring(0, i).replace("proxy_pass", "").trim();
                if(proxy_pass1.contains("https")){
                    logger.error("nginx.head.proxy_pass 不支持https");
                    throw new RuntimeException("nginx.head.proxy_pass 不支持https");
                }
                o.getNginx().setProxy_pass(proxy_pass1.replace("http://",""));

            } catch (JSchException e) {
                e.printStackTrace();
                throw new RuntimeException("nginx 配置异常");
            }


            try {
                SshInfo sshInfo = o.getSshInfo();
                JSch jsch = new JSch();
                Session session = jsch.getSession(sshInfo.getUser(), sshInfo.getHost());
                session.setPassword(sshInfo.getPasswd());
                Properties sshConfig = new Properties();
                sshConfig.put("StrictHostKeyChecking", "no");
                session.setConfig(sshConfig);
                session.connect();
                o.setSession(session);
            } catch (JSchException e) {
                e.printStackTrace();
                throw new RuntimeException("nginx 配置异常");
            }

            if ("springboot".equalsIgnoreCase(o.getType())) {
                o.getSpringBootNodes().forEach(bootNode -> {
                    SshInfo sshInfo1 = bootNode.getSshInfo();
                    JSch jSch = new JSch();
                    try {
                        Session session = jSch.getSession(sshInfo1.getUser(), sshInfo1.getHost());
                        session.setPassword(sshInfo1.getPasswd());
                        Properties config = new Properties();
                        config.put("StrictHostKeyChecking", "no");
                        session.setConfig(config);
                        session.connect();
                        bootNode.setSession(session);
                    } catch (JSchException e) {
                        e.printStackTrace();
                        throw new RuntimeException("tomcat 配置异常");
                    }
                });
            } else {
                o.getTomcatNodes().forEach(tomcatNode -> {
                    SshInfo sshInfo1 = tomcatNode.getSshInfo();
                    JSch tomcatJsch = new JSch();
                    try {
                        Session tomcatSession = tomcatJsch.getSession(sshInfo1.getUser(), sshInfo1.getHost());
                        tomcatSession.setPassword(sshInfo1.getPasswd());
                        Properties config = new Properties();
                        config.put("StrictHostKeyChecking", "no");
                        tomcatSession.setConfig(config);
                        tomcatSession.connect();
                        tomcatNode.setSession(tomcatSession);
                    } catch (JSchException e) {
                        e.printStackTrace();
                        throw new RuntimeException("tomcat 配置异常");
                    }
                });
            }





            if (collect.contains("-s")) {
                o.setMustReload(true);
            }
            this.config = o;
        } catch (IOException e) {
            throw new RuntimeException("config 文件加载异常", e);
        }
    }


    private List<Node> getNodes() {
        List<Node> nodes = new ArrayList<>();
        if ("springboot".equalsIgnoreCase(config.getType())) {
            config.getSpringBootNodes().forEach(springBootNode -> {
                nodes.add(springBootNode);
            });
        } else {
            config.getTomcatNodes().forEach(springBootNode -> {
                nodes.add(springBootNode);
            });
        }
        return nodes;
    }

    public void deploy() {
        //检查节点
        List<Node> nodeList = getNodes();
        if (nodeList.size() < 1) {
            throw new RuntimeException("config.tomcatNodes 配置节点小于1个错误");
        }

        if (nodeList.size() > 1) {//如果是多个节点部署，尽量保证第一个节点是启动状态
            //检查接口是否运行
            String s = checkUrl(nodeList.get(0).getCheckUrl());
            if ("error".equals(s)) {
                logger.warn("第一个节点未启动,不符合要求,会影响生产环境 config.tomcatNodes[0]" + JSON.toJSONString(nodeList.get(0)));
            }

            genAndLoadNginx(nodeList.get(0));
            logger.info("切换负载配置到第1个节点上，开始等待10秒...");
            try {
                Thread.sleep(10000L);
            } catch (InterruptedException e) {
                logger.error(e.getMessage());
            }

            List<Node> blist = new ArrayList<>();
            for (int i = 1; i < nodeList.size(); i++) {
                deployUpgrade(nodeList.get(i));
                logger.info("升级第{}节点", (i + 1));
                blist.add(nodeList.get(i));
            }
            Node[] nodes = new Node[blist.size()];
            //如果是有备份节点
            if (!blist.isEmpty()) {
                blist.toArray(nodes);
                genAndLoadNginx(nodes);
                logger.info("切换负载配置到备用节点,开始等待10秒...");
                try {
                    Thread.sleep(10000L);
                } catch (InterruptedException e) {
                    logger.error(e.getMessage());
                }
            }
        }


        deployUpgrade(nodeList.get(0));
        Node[] nodes = new Node[nodeList.size()];
        nodeList.toArray(nodes);
        genAndLoadNginx(nodes);

        //备份老版本
        String backPath = config.getBackPath();
        File backFile = new File(backPath);
        if (!backFile.exists()) {
            backFile.mkdirs();
        }
        //对比版本 如果版本不一样才需要备份
        String[] list = backFile.list();
        Set<String> collect = Arrays.stream(list).collect(Collectors.toSet());
        if (!collect.toString().contains(config.getUpdateMd5())) {
            DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
            String format = dateFormat.format(new Date());
            File file = new File(config.getUploadFile());
            File backFileDest = new File(backFile, file.getName() + format + "_" + config.getUpdateMd5());
            logger.info("备份版本文件");
            exeCmd("cp  " + config.getUploadFile() + " " + backFileDest.getAbsolutePath(), config.getSession());
        }
        System.exit(0);
    }

    public void reback(String name) {
        List<Node> nodeList = getNodes();
        if (nodeList.size() < 2) {
            throw new RuntimeException("config.tomcatNodes 配置节点小于2个错误");
        }

        String s = checkUrl(nodeList.get(0).getCheckUrl());
        if ("error".equals(s)) {
            throw new RuntimeException("第一个节点作为主节点,检查运行状态不正常,会影响生产环境 config.tomcatNodes[0]" + JSON.toJSONString(nodeList.get(0)));
        }

        genAndLoadNginx(nodeList.get(0));

        List<Node> blist = new ArrayList<>();
        for (int i = 1; i < nodeList.size(); i++) {
            deployUpgrade(nodeList.get(i));
            blist.add(nodeList.get(i));
        }
        Node[] nodes = new Node[blist.size()];
        blist.toArray(nodes);

        genAndLoadNginx(nodes);

        deployUpgrade(nodeList.get(0));

        nodes = new Node[nodeList.size()];
        nodeList.toArray(nodes);
        genAndLoadNginx(nodes);
    }

    private void genAndLoadNginx(Node... nodes) {
        String s1 = genNginxConfig(nodes);
        try {
            String configFile = config.getNginx().getConfigFile();

            if ("127.0.0.1".equals(config.getNginx().getSshInfo().getHost())) {
                FileUtils.writeByteArrayToFile(new File(configFile), s1.getBytes(Charset.forName("utf-8")));
            } else {
                int i = configFile.lastIndexOf("/");
                ByteArrayInputStream bufferedInputStream = new ByteArrayInputStream(s1.getBytes("utf-8"));
                ;
                upload(configFile.substring(0, i), configFile.substring(i + 1), bufferedInputStream, config.getSession());
            }
            exeCmd("nginx -s reload", config.getSession());
            logger.info("nginx负载切换完成");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }

    }


    public String genNginxConfig(Node... nodes) {
        StringBuilder stringBuilder = new StringBuilder("upstream "+config.getNginx().getProxy_pass()+"\n");
        stringBuilder.append("{\n");
        Arrays.stream(nodes).forEach(tomcatNode -> {
            stringBuilder.append("    server " + tomcatNode.getNginxProxyPass() + ";\n");
        });
        stringBuilder.append("}\n");
        stringBuilder.append(config.getNginx().getHead());
        return stringBuilder.toString();
    }

//    //
//    public void deployBack(TomcatNode tomcatNode) {
//        String updateMd5 = getUpdateMd5();
//        String tomcatMd5 = getTomcatMd5(tomcatNode);
//        //如果文件不一致，更新启动升级
//        if (!updateMd5.equals(tomcatMd5)) {
//
//            exeCmd("source /etc/profile;"+tomcatNode.getHomePath() + "/bin/shutdown.sh",tomcatNode.getSession());
//            exeCmd("rm -rf " + tomcatNode.getHomePath() + "/work/*",tomcatNode.getSession());
//            exeCmd("cp  " + config.getUploadFile() + " " + tomcatNode.getFilePath(),tomcatNode.getSession());
//            exeCmd(tomcatNode.getHomePath() + "/bin/startup.sh",tomcatNode.getSession());
//            try {
//                Thread.sleep(5000L);
//                String s = checkUrl(tomcatNode.getCheckUrl());
//                int max = 120;
//                while ("error".equals(s) && max > 0) {
//                    max--;
//                    Thread.sleep(1000L);
//                    s = checkUrl(tomcatNode.getCheckUrl());
//                }
//                if ("error".equals(s)) {
//                    logger.error("部署节点失败 ｛｝", JSON.toJSONString(tomcatNode));
//                    throw new RuntimeException("部署节点失败");
//                }else{
//                    logger.error("服务测试通过", JSON.toJSONString(tomcatNode));
//                }
//
//            } catch (InterruptedException e) {
//                logger.error("任务失败", JSON.toJSONString(tomcatNode));
//                throw new RuntimeException("任务失败");
//            }
//        }
//    }

    //
    public void deployUpgrade(Node node) {

        String s = checkUrl(node.getCheckUrl());
        //如果服务尚未运行

        String updateMd5 = getUpdateMd5();
        String tomcatMd5 = getTomcatMd5(node);
        //如果文件不一致，更新启动升级
        String[] tomcatIdArray = null;
        try {
            String tomcatIds = node.getShellId();
            if (tomcatIds != null) {
                tomcatIdArray = tomcatIds.trim().split("\\s");
            }
        } catch (Throwable e) {
            logger.info("检查不存在 进程id");
        }
        logger.info("进程id格式化："+JSON.toJSONString(tomcatIdArray));

        // 如果服务未启动 强制重新发包启动
        // 文件不一致需要重新打包发布
        // 如果服务替换了包，但是服务因为特殊原因没有重启也可以强制重启
//        if ("error".equals(s)||!updateMd5.equals(tomcatMd5)||config.isMustReload()) {
        for (String tomcatId : tomcatIdArray) {
            //关掉服务(延时10秒保证线上服务线程运行都结束了)
            if (tomcatId != null && tomcatId.length() > 0) {
                exeCmd("kill -9 " + tomcatId, node.getSession());
            }
            //如果文件存在
            if (tomcatMd5 != null) {
                //备份老版本
                String backPath = config.getBackPath();
                File backFile = new File(backPath);
                if (!backFile.exists()) {
                    backFile.mkdirs();
                }
                //对比版本 如果版本不一样才需要备份
                String[] list = backFile.list();
                Set<String> collect = Arrays.stream(list).collect(Collectors.toSet());
                //如果这个版本没有备份过
                if (!collect.toString().contains(tomcatMd5)) {
                    DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
                    String format = dateFormat.format(new Date());
                    File backFileDest = new File(backFile, node.getName() + format + "_" + tomcatMd5);
                    exeCmd("cp  " + node.getFilePath() + " " + backFileDest.getAbsolutePath(), node.getSession());
                }
                //清理文件
                node.clear();

            }
            //重启服务

            if ("127.0.0.1".equals(node.getSshInfo().getHost()) || "localhost".equals(node.getSshInfo().getHost())) {
                exeCmd("cp  " + config.getUploadFile() + " " + node.getFilePath(), node.getSession());
            } else {
                FileInputStream fileInputStream = null;
                try {
                    fileInputStream = new FileInputStream(config.getUploadFile());
                    int indexOf = node.getFilePath().lastIndexOf("/");
                    upload(node.getFilePath().substring(0, indexOf), node.getFilePath().substring(indexOf + 1), fileInputStream, node.getSession());
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
            tomcatId = null;

//        }else{
//            logger.info("当前节点正常无需升级");
//        }

        }
        //杀掉的服务或者根本没有启动的服务需要重启启动
//        if (tomcatIdArray == null || tomcatIdArray.length == 0) {
            node.star();
            try {
                Thread.sleep(5000L);
                s = checkUrl(node.getCheckUrl());
                int max = 120;
                while ("error".equals(s) && max > 0) {
                    max--;
                    Thread.sleep(1000L);
                    s = checkUrl(node.getCheckUrl());
                }
                if ("error".equals(s)) {
                    logger.error("部署节点失败 ｛｝", JSON.toJSONString(node));
                    throw new RuntimeException("部署节点失败");
                } else {
                    logger.info("测试正常");
                }

            } catch (InterruptedException e) {
                logger.error("任务失败", JSON.toJSONString(node));
                throw new RuntimeException("任务失败");
            }
//        }
    }

    //检查地址是否能访问
    String checkUrl(String url) {
        String result = "ok";
        BufferedReader in = null;
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.setConnectTimeout(1000);
            connection.setReadTimeout(1000);
            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;

            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            logger.error("测试连接请求失败,{}", url);
            return "error";
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    private String getUpdateMd5() {
        if (config.getUpdateMd5() == null) {
            String md5 = exeCmd("md5sum " + config.getUploadFile(), config.getSession());
            md5 = md5.split("\\s")[0];
            config.setUpdateMd5(md5);
        }
        return config.getUpdateMd5();
    }

    private String getBackMd5() {
        if (config.getBackMd5() == null) {
            String md5 = exeCmd("md5sum " + config.getBackFile(), config.getSession());
            md5 = md5.split("\\s")[0];
            config.setBackMd5(md5);
        }
        return config.getBackMd5();
    }

    public boolean upload(String directory, String sftpFileName, InputStream input, Session session) {
        try {
            logger.info("open sftp {}", session.getHost());
            Channel channel = session.openChannel("sftp");
            channel.connect();

            ChannelSftp sftp = (ChannelSftp) channel;
            if (directory != null && !"".equals(directory)) {
                sftp.cd(directory);
            }
            sftp.put(input, sftpFileName);  //上传文件
            channel.disconnect();
            logger.info("upload {}/{}", directory, sftpFileName);
            return true;
        } catch (SftpException | JSchException e) {
            return false;
        }
    }


    private String getTomcatMd5(Node tomcatNode) {
        if (tomcatNode.getMd5() == null) {
            String file = exeCmd("ls " + tomcatNode.getFilePath(), tomcatNode.getSession());
            if (file == null || file.length() == 0) {
                logger.info("文件不存在 {}", tomcatNode.getFilePath());
            } else {
                String md5 = exeCmd("md5sum " + tomcatNode.getFilePath(), tomcatNode.getSession());
                md5 = md5.split("\\s")[0];
                tomcatNode.setMd5(md5);
            }

        }
        return tomcatNode.getMd5();
    }


    public static String exeCmd(String cmd, Session session) {

        BufferedReader br = null;
        try {

            StringBuffer buf = new StringBuffer(1024);
            logger.info(cmd);
            ChannelExec exec = (ChannelExec) session.openChannel("exec");
            exec.setCommand(cmd);
            exec.setInputStream(null);
            exec.setErrStream(null);
            InputStream in = exec.getInputStream();
            exec.connect();

            int res = -1;
            byte[] tmp = new byte[1024];
            while (true) {
                while (in.available() > 0) {
                    int i = in.read(tmp, 0, 1024);
                    if (i < 0)
                        break;
                    buf.append(new String(tmp, 0, i));
                }
                if (exec.isClosed()) {
                    res = exec.getExitStatus();
                    break;
                }
            }
            if (buf.length() == 0) {
                in = exec.getErrStream();
                while (true) {
                    while (in.available() > 0) {
                        int i = in.read(tmp, 0, 1024);
                        if (i < 0)
                            break;
                        buf.append(new String(tmp, 0, i));
                    }
                    if (exec.isClosed()) {
                        res = exec.getExitStatus();
                        break;
                    }
                }
            }
            exec.disconnect();

            logger.info(buf.toString());
            return buf.toString();
        } catch (IOException | JSchException e) {
            logger.error("#ErrMsg=" + e.getMessage());
            throw new RuntimeException("执行命令失败" + cmd);
        }
    }
}
